<?php
namespace PsiFramework\Classes\Util\Tools;

use \PsiFramework\Classes\Util\Manager\ApplicationManager;
use \PsiFramework\Classes\Util\Manager\ObjectManager;

/**
 * PsiFramework
 * Php Simple Framework
 *
 * @author Rémi San
 * @version beta
 */

/**
 * The ClassGenerator class generates other classes
 *
 * @package DAO
 */
class ClassGenerator
{
    
    protected static function write($fileName, $completeCode, $overwrite=true)
    {
        if ($overwrite || !file_exists($fileName)) {
            $fh = fopen($fileName, 'w');
            fwrite($fh, '<?php'."\n".$completeCode);
            fclose($fh);
            chmod($fileName, 0777);
        }
    }
    
    public static function getClassCode(/*string*/ $objectName, /*bool*/ $inherit=false, /*bool*/ $inherited=false, /*bool*/ $write=false)
    {
    
        $applicationManager = ApplicationManager::getInstance();
        $application = $applicationManager->getApplication();
        $objectParameters = $application->getObjectParameter($objectName);
    
        $completeClassName = $objectParameters->getClass();
        $shortClassName = substr($completeClassName, strrpos($completeClassName, '.')+1);
        $namespace = str_ireplace('.', '\\', substr($completeClassName, 0, strrpos($completeClassName, '.')));
    
        $uses = array();
        $uses['Object'] = 'Classes\\Model\\Object';
        
        $psiShortClassName = 'Object';
        if ($inherit) {
            
            $psiNamespace = $namespace.'\\Psi';
            $psiShortClassName = 'Psi'.$shortClassName;
            
            if (!$inherited) {
                $namespace = $psiNamespace;
                $shortClassName = $psiShortClassName;
                $psiShortClassName = 'Object';
            } else {
                $uses[$psiShortClassName] = $psiNamespace.'\\'.$psiShortClassName;
            }
        }
        
        $importCode = 'namespace PsiFramework\\'.$namespace.";\n\n";
    
        $classCode  = '/**'."\n";
        $classCode .= ' * PsiFramework'."\n";
        $classCode .= ' * Php Simple Framework'."\n";
        $classCode .= ' *'."\n";
        $classCode .= ' * @author Rémi San'."\n";
        $classCode .= ' * @version beta'."\n";
        $classCode .= ' */'."\n";
        $classCode .= "\n";
        $classCode .= '/**'."\n";
        $classCode .= ' * The '.$shortClassName.' class is a Model Business Class'."\n";
        $classCode .= ' *'."\n";
        $classCode .= ' * @package Model'."\n";
        $classCode .= ' */'."\n";
        $classCode .= "\n";
        $classCode .= 'class '.$shortClassName.' extends '.$psiShortClassName."\n";
        $classCode .= '{'."\n";
        $classCode .= "\n";
    
        $params = array();
        $paramMaxTypeLength = 0;
        $paramMaxNameLength = 0;
    
        foreach ($objectParameters->getParameters() as $attribute) {
    
            $type = $attribute->type;
            if (!array_key_exists($type, $uses) && !$attribute->isPrimitive()) {
                $typeClass = null;
                if (!$attribute->isPrimitiveObject()) {
                    try {
                        $innerObjectParameters = $application->getObjectParameter($type);
                    } catch (\Exception $e) {
                        throw new \Exception($attribute->name.' : '.$e->getMessage());
                    }
                    $typeClass = $innerObjectParameters->getClass();
                } else {
                    $typeClass = ObjectManager::getInstance()->getPrimitiveObjectClass($type);
                }
    
                if (!isNullOrEmptyString($typeClass)) {
                    $uses[$type] = str_ireplace('.', '\\', $typeClass);
                }
            }
    
            $param = array('name'=>$attribute->name, 'type'=>$attribute->getStringType());
            if (strlen($param['name'])>$paramMaxNameLength) {
                $paramMaxNameLength = strlen($param['name']);
            }
            if (strlen($param['type'])>$paramMaxTypeLength) {
                $paramMaxTypeLength = strlen($param['type']);
            }
            $params[] = $param;
        }
    
        foreach ($uses as $name=>$class) {
            $importCode .= 'use \\PsiFramework\\'.$class.";\n";
        }
    
        $variablesDeclaration = '';
    
        $constructorComment  = "\t/**\n";
        $constructorComment .= "\t * ".$shortClassName." Constructor\n";
        $constructorComment .= "\t *\n";
        $constructorSignature = "\tpublic function __construct(\n";
        $constructorBody = "\t{\n";
        
        if ($inherit && $inherited) {
            $constructorBody .= "\t\tparent::__construct(";
        }
    
        $functionsDeclaration = '';
    
        $paramCount = 1;
        foreach ($params as $param) {
            $typeSeparator = getBlankSpace($paramMaxTypeLength-strlen($param['type']));
            $nameSeparator = getBlankSpace($paramMaxNameLength-strlen($param['name']));
    
            $variablesDeclaration .= "\tprotected /*".$param['type'].'*/'.$typeSeparator.' $_'.$param['name']."; \n";
            $constructorComment .= "\t *@param ".$param['type'].$typeSeparator.' $'.$param['name'].$nameSeparator.' the '.$param['name']."\n";
    
            $constructorSignature .= "\t\t/*".$param['type'].'*/'.$typeSeparator.' $'.$param['name'].'=null';
            if($paramCount != count($params)) $constructorSignature .= ',';
            $constructorSignature .="\n";
    
            if (!$inherit || !$inherited) {
                $constructorBody .= "\t\t".'$this->_'.$param['name'].$nameSeparator.' = $'.$param['name']."; \n";
            } else {
                if ($paramCount>1) {
                    $constructorBody .= ', ';
                }
                $constructorBody .= '$'.$param['name'];
            }
    
            $capitalizedName = ucfirst($param['name']);
    
            $functionsDeclaration .= "\t".'/**'."\n";
            $functionsDeclaration .= "\t".' * Setter for the member variable $'.$param['name']."\n";
            $functionsDeclaration .= "\t".' *'."\n";
            $functionsDeclaration .= "\t".' * @param '.$param['type'].' $'.$param['name'].' the '.$param['name']."\n";
            $functionsDeclaration .= "\t".' *'."\n";
            $functionsDeclaration .= "\t".' * @return void'."\n";
            $functionsDeclaration .= "\t".' */'."\n";
            $functionsDeclaration .= "\t".'public function set'.$capitalizedName.'(/*'.$param['type'].'*/ $'.$param['name'].')'."\n";
            $functionsDeclaration .= "\t".'{'."\n";
            $functionsDeclaration .= "\t\t".'$this->_'.$param['name'].' = $'.$param['name'].';'."\n";
            $functionsDeclaration .= "\t".'}'."\n";
            $functionsDeclaration .= "\n";
    
            $getterPrefix = 'get';
            if ($param['type'] == 'boolean' || $param['type'] == 'bool') {
                $getterPrefix = 'is';
            }
            
            $functionsDeclaration .= "\t".'/**'."\n";
            $functionsDeclaration .= "\t".' * Getter for the member variable $'.$param['name']."\n";
            $functionsDeclaration .= "\t".' *'."\n";
            $functionsDeclaration .= "\t".' * @return '.$param['type']."\n";
            $functionsDeclaration .= "\t".' */'."\n";
            $functionsDeclaration .= "\t".'public function '.$getterPrefix.$capitalizedName.'()'."\n";
            $functionsDeclaration .= "\t".'{'."\n";
            $functionsDeclaration .= "\t\t".'return $this->_'.$param['name'].';'."\n";
            $functionsDeclaration .= "\t".'}'."\n";
            $functionsDeclaration .= "\n\n";
    
            $paramCount++;
        }
    
        if ($inherit && $inherited) {
            $constructorBody .= ");\n";
        }
        
        $constructorComment .= "\t *\n";
        $constructorComment .= "\t *@return ".$shortClassName."\n";
        $constructorComment .= "\t */\n";
        $constructorSignature .= "\t)\n";
        $constructorBody .= "\t}\n";
    
        if (!$inherit || !$inherited) {
            $classCode .= $variablesDeclaration."\n\n";
        }
        
        //TODO voir s'il faut garder ou supprimer le constructeur
        if ($inherit && $inherited) {
            $constructorBody = '';
        }
        
        $classCode .= $constructorComment.$constructorSignature.$constructorBody."\n";
        
        if (!$inherit || !$inherited) {
            $classCode .= "\n";
            $classCode .= $functionsDeclaration;
        
            $classCode .= "\t".'/**'."\n";
            $classCode .= "\t".' *The function that returns the object as a string'."\n";
            $classCode .= "\t".' *'."\n";
            $classCode .= "\t".' *@return string'."\n";
            $classCode .= "\t".' */'."\n";
            $classCode .= "\t".'public function toString()'."\n";
            $classCode .= "\t".'{'."\n";
            $classCode .= "\t\t".'return $this->_id;'."\n";
            $classCode .= "\t".'}'."\n";
            $classCode .= "\n";
        }
        
        $classCode .= '}';
    
        $completeCode = $importCode."\n".$classCode;
        
        if (!isNullOrEmptyString($completeCode) && $write) {
            $fileName = getClassFile($namespace.'\\'.$shortClassName, '');
            self::write($fileName, $completeCode, !($inherit && $inherited));
        }
        
        return $completeCode;
    }
    
    public static function getDAOClassCode(/*string*/ $objectName, /*bool*/ $write=false)
    {
    
        $applicationManager = ApplicationManager::getInstance();
        $application = $applicationManager->getApplication();
        $objectParameters = $application->getObjectParameter($objectName);
    
        $referenceClassName = $objectParameters->getClass();
        $shortReferenceClassName = substr($referenceClassName, strrpos($referenceClassName, '.')+1);
    
        $completeClassName = $objectParameters->getDAO();
        $shortClassName = substr($completeClassName, strrpos($completeClassName, '.')+1);
        $namespace = str_ireplace('.', '\\', substr($completeClassName, 0, strrpos($completeClassName, '.')));
    
        $uses = array();
        $uses['AbstractDAO'] = 'Classes\\DAO\\AbstractDAO';
        $uses['Object'] = 'Classes\\Model\\Object';
        $uses['ObjectParameters'] = 'Classes\\Util\Parameters\\ObjectParameters';
        $uses[$objectParameters->getName()] = str_ireplace('.', '\\', $referenceClassName);
    
        $importCode = 'namespace PsiFramework\\'.$namespace.";\n\n";
    
        $classCode  = '/**'."\n";
        $classCode .= ' * PsiFramework'."\n";
        $classCode .= ' * Php Simple Framework'."\n";
        $classCode .= ' *'."\n";
        $classCode .= ' * @author Rémi San'."\n";
        $classCode .= ' * @version beta'."\n";
        $classCode .= ' */'."\n";
        $classCode .= "\n";
        $classCode .= '/**'."\n";
        $classCode .= ' * The '.$shortClassName.' class is the Data Access Object for class '.$shortReferenceClassName."\n";
        $classCode .= ' *'."\n";
        $classCode .= ' * @package DAO'."\n";
        $classCode .= ' */'."\n";
        $classCode .= 'class '.$shortClassName.' extends AbstractDAO'."\n";
        $classCode .= '{'."\n";
        $classCode .= "\n";
    
        $classCode .= "\t".'protected static $_singleton = null;'."\n";
        $classCode .= "\n";
        $classCode .= "\t".'/**'."\n";
        $classCode .= "\t".' * Function to be called instead of the constructor.'."\n";
        $classCode .= "\t".' *'."\n";
        $classCode .= "\t".' * @return '.$shortClassName.' instance'."\n";
        $classCode .= "\t".' */'."\n";
        $classCode .= "\t".'public static function getInstance(ObjectParameters $objectParameters)'."\n";
        $classCode .= "\t".'{'."\n";
        $classCode .= "\t\t".'if (self::$_singleton==null) {'."\n";
        $classCode .= "\t\t\t".'self::$_singleton = new '.$shortClassName.'($objectParameters);'."\n";
        $classCode .= "\t\t".'}'."\n";
        $classCode .= "\t\t".'return self::$_singleton;'."\n";
        $classCode .= "\t".'}'."\n";
    
        $classCode .= '}';
    
        foreach ($uses as $name=>$class) {
            $importCode .= 'use \\PsiFramework\\'.$class.";\n";
        }
    
        $completeCode = $importCode."\n".$classCode;
        
        if (!isNullOrEmptyString($completeCode) && $write) {
            $fileName = getClassFile($namespace.'\\'.$shortClassName, '');
            self::write($fileName, $completeCode);
        }
        
        return $completeCode;
    }
}